1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package org.webmacro.engine;
25
26 import org.webmacro.*;
27 import org.webmacro.util.Named;
28
29 import java.io.IOException;
30
31
32
33 /***
34 * A Variable is a reference into a Propertymap.
35 * <p>
36 * A variable name contains a list of names separated by dots, for
37 * example "$User.Identity.email.address" is the list: User, Identity,
38 * email, and address.
39 * <p>
40 * PLEASE NOTE: Case-sensitivity is enforced. "User" is the the same
41 * name as "user".
42 * <p>
43 * What that means: When a template is interpreted, it is interpreted
44 * in terms of data in a hashtable/map called the "context". This is
45 * actually a Map of type Map. The context contains all the
46 * local variables that have been set, as well as other information
47 * that Macros may use to evaluate the request.
48 * <p>
49 * Variable depends heavily on Property introspection: It is defined
50 * as a list of one or more names (separated by dots when written).
51 * <p>
52 * Those names are references to sub-objects within the context. The
53 * Variable instance, when interpreted, will decend through the context
54 * following fields, method references, or hash table look-ups based
55 * on its names.
56 * <p>
57 * For example, the variable "$User.Identity.email.address" implies
58 * that there is a "User" object under the Map--either it is
59 * a field within the map, or the map has a getUser() method, or
60 * the User can be obtained by calling Map.get("User").
61 * <p>
62 * The full expansion of $User.Identity.email.address might be:<pre>
63 *
64 * Map.get("User").getIdentity().get("email").address
65 *
66 * </pre>. Variable (actually the Property class it uses) will figure
67 * out how to decend through the object like this until it finds the
68 * final reference--which is the "value" of the variable.
69 * <p>
70 * When searchin for subfields Variable prefers fields over getFoo()
71 * methods, and getFoo() over get("Foo").
72 *
73 */
74 public abstract class Variable implements Macro, Visitable
75 {
76
77
78
79
80
81
82 final static public Object PROPERTY_TYPE = new Object();
83 final static public Object LOCAL_TYPE = new Object();
84
85 /***
86 * The name of this variable.
87 */
88 private String _vname;
89
90 /***
91 * The name as an array
92 */
93 protected Object[] _names;
94
95 /***
96 * Create a variable with the supplied name. The elements of the name
97 * are either strings, or a method reference.
98 */
99 Variable (Object names[])
100 {
101 _names = names;
102
103 }
104
105 /***
106 * Return the property names for this variable. These are stringified
107 * names corresponding to the names of the variable; if one of the
108 * elements of the variable name is a method call then the name of
109 * the method is inserted at that point as if it were a property name.
110 */
111 static final String[] makePropertyNames (Object names[])
112 {
113 String[] sn = new String[names.length];
114 for (int i = 0; i < sn.length; i++)
115 {
116 sn[i] = (names[i] instanceof Named) ?
117 ((Named) names[i]).getName() : (String) names[i];
118 }
119 return sn;
120 }
121
122 public final String[] getPropertyNames ()
123 {
124 return makePropertyNames(_names);
125 }
126
127 /***
128 * Like getPropertyNames, but only works if isSimpleName is true
129 */
130 public final String getName ()
131 {
132 return (_names[0] instanceof Named) ?
133 ((Named) _names[0]).getName()
134 : (String) _names[0];
135 }
136
137 /***
138 * Returns true if the Variable describes a simple name (one with only
139 * one element)
140 */
141 public boolean isSimpleName ()
142 {
143 return (_names.length == 1);
144 }
145
146 /***
147 * Looks in the hashTable (context) for a value keyed to this variables
148 * name and returns the value string. If the resulting value is a Macro,
149 * recursively call its evaluate method.
150 * @return String
151 */
152 final public Object evaluate (Context context) throws PropertyException
153 {
154 try
155 {
156 Object val = getValue(context);
157 if (val instanceof Macro)
158 {
159 val = ((Macro) val).evaluate(context);
160 }
161 return val;
162 }
163 catch (NullPointerException e)
164 {
165
166 context.getEvaluationExceptionHandler()
167 .evaluate(this, context,
168 new PropertyException.NullValueException(getVariableName()));
169 return null;
170 }
171 catch (PropertyException e)
172 {
173
174 if (e instanceof PropertyException.UndefinedVariableException)
175 {
176 PropertyException.UndefinedVariableException uve = (PropertyException.UndefinedVariableException) e;
177 if (_names.length > 1)
178 uve.setMessage(
179 "Attempted to reference a property or method of an undefined variable: $" + _names[0]);
180 else
181 uve.setMessage(
182 "Attempted to evaluate an undefined variable: $" + _names[0]);
183 }
184 context.getEvaluationExceptionHandler()
185 .evaluate(this, context, e);
186 return null;
187 }
188 catch (Exception e)
189 {
190
191 context.getEvaluationExceptionHandler()
192 .evaluate(this, context,
193 new PropertyException("Variable: exception evaluating "
194 + getVariableName(), e));
195 return null;
196 }
197 }
198
199 /***
200 * Look in the hashtable (context) for a value keyed to this variables
201 * name and write its value to the stream.
202 * @exception PropertyException is required data is missing
203 * @exception IOException if could not write to output stream
204 */
205 final public void write (FastWriter out, Context context)
206 throws PropertyException, IOException
207 {
208 try
209 {
210 Object val = getValue(context);
211 if (val instanceof Macro)
212 ((Macro) val).write(out, context);
213 else
214 {
215 if (val != null)
216 {
217 String v = val.toString();
218 if (v != null)
219 out.write(v);
220 else
221 {
222 out.write(context.getEvaluationExceptionHandler()
223 .expand(this, context,
224 new PropertyException.NullToStringException(getVariableName())));
225 }
226 }
227 else
228 {
229 if (isSimpleName())
230 {
231
232
233 out.write(context.getEvaluationExceptionHandler()
234 .expand(this, context,
235 new PropertyException.NoSuchVariableException(getVariableName())));
236 }
237 else
238 {
239
240 out.write(context.getEvaluationExceptionHandler()
241 .expand(this, context,
242 new PropertyException.NullValueException(getVariableName())));
243 }
244 }
245 }
246 }
247 catch (PropertyException e)
248 {
249 if (e instanceof PropertyException.UndefinedVariableException)
250 {
251 PropertyException.UndefinedVariableException uve = (PropertyException.UndefinedVariableException) e;
252 if (_names.length > 1)
253 uve.setMessage(
254 "Attempted to write a property or method value of an undefined variable: $" + _names[0]);
255 else
256 uve.setMessage(
257 "Attempted to write an undefined variable: $" + _names[0]);
258 }
259 out.write(context.getEvaluationExceptionHandler()
260 .expand(this, context, e));
261 }
262 catch (Exception e)
263 {
264
265
266 out.write(context.getEvaluationExceptionHandler()
267 .expand(this, context, e));
268 }
269 }
270
271 /***
272 * Helper method to construct a String name from a Object[] name
273 */
274 final static String makeName (Object[] names)
275 {
276 StringBuffer buf = new StringBuffer();
277 for (int i = 0; i < names.length; i++)
278 {
279 if (i != 0)
280 {
281 buf.append(".");
282 }
283 buf.append(names[i]);
284 }
285 return buf.toString();
286 }
287
288 /***
289 * The code to get the value represented by the variable from the
290 * supplied context.
291 */
292 public abstract Object getValue (Context context) throws PropertyException;
293
294 /***
295 * The code to set the value represented by the variable in the
296 * supplied context.
297 */
298 public abstract void setValue (Context c, Object v) throws PropertyException;
299
300 /***
301 * Return the String name of the variable prefixed with a string
302 * representing its type. For example local:a.b.c
303 */
304 public abstract String toString ();
305
306 /***
307 * Return the canonical name for this variable
308 */
309 public synchronized String getVariableName ()
310 {
311 if (_vname == null) {
312 _vname = makeName(_names).intern();
313 }
314 return _vname;
315 }
316
317 public void accept (TemplateVisitor v)
318 {
319 v.visitVariable(this, _names);
320 }
321
322 }
323
324
325